﻿using System;
using System.Linq;
using System.Collections.Generic;
using T.FAS.Runtime.Metadata.SPI;
using T.FAS.Runtime.Metadata.Entity;

namespace T.FAS.Runtime.DataModel.Dao
{
    /// <summary>
    /// 
    /// </summary>
    public class CompareTool
    {
        /// <summary>
        /// 比较集合内容, true：有变更 false：无变更
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="oldDatas"></param>
        /// <param name="newDatas"></param>
        /// <param name="identifierEqualFunc">判断对象标识是否相等，true:相等，false:不相等</param>
        /// <param name="bodyEqualFunc">判断对象实体是否相等，true:相等，false:不相等</param>
        /// <param name="needAdd"></param>
        /// <param name="needDelete"></param>
        /// <param name="needUpdateOld"></param>
        /// <returns></returns>
        public static bool Compare<T>(List<T> oldDatas,
                                List<T> newDatas,
                                Func<T, T, bool> identifierEqualFunc,
                                Func<T, T, bool> bodyEqualFunc,
                                out List<T> needAdd,
                                out List<T> needDelete,
                                out List<T> needUpdateOld,
                                out List<Tuple<T, T>> needUpdateTuple)
        {
            if (oldDatas == null) oldDatas = new List<T>();
            if (newDatas == null) newDatas = new List<T>();

            needAdd = new List<T>();
            needDelete = new List<T>();
            needUpdateOld = new List<T>();
            needUpdateTuple = new List<Tuple<T, T>>();
            List<T> sameDataInOld = new List<T>();

            foreach (T newData in newDatas)
            {
                bool existFlag = false;
                bool changeFlag = false;
                T tmpOldData = default;
                foreach (T oldData in oldDatas)
                {
                    if (identifierEqualFunc(newData, oldData))
                    {
                        changeFlag = !bodyEqualFunc(newData, oldData);
                        tmpOldData = oldData;
                        existFlag = true;
                        break;
                    }
                }
                if (existFlag)
                {
                    sameDataInOld.Add(tmpOldData);
                    if (changeFlag)
                    {
                        needUpdateOld.Add(newData);
                        needUpdateTuple.Add(Tuple.Create(tmpOldData, newData));
                    }
                }
                else
                    needAdd.Add(newData);
            }
            needDelete = oldDatas.Where(old => !sameDataInOld.Contains(old)).ToList();
            return needAdd.Count + needDelete.Count + needUpdateOld.Count > 0;
        }

        public static List<MetaDataUpdateItem> GenerateUpdateItem<T>(object sources, UpdateOpType opType, MetaUpdateType metaUpdateType)
        {
            List<MetaDataUpdateItem> result = new List<MetaDataUpdateItem>();
            if (sources == null) return result;
            string updateKey = metaUpdateType.ToString().Replace("_this", "").Split('_')?.Last();
            if (sources is List<T> list)
            {
                foreach (T item in list)
                {
                    if (opType == UpdateOpType.ADD)
                    {
                        result.Add(new MetaDataUpdateItem
                        {
                            UpdateType = metaUpdateType,
                            UpdateKey = updateKey,
                            NewValue = item,
                        });
                    }
                    else if (opType == UpdateOpType.DELETE)
                    {
                        result.Add(new MetaDataUpdateItem
                        {
                            UpdateType = metaUpdateType,
                            UpdateKey = updateKey,
                            OldValue = item,
                        });
                    }
                }
            }
            else if (sources is List<Tuple<T, T>> tupleList)
            {
                foreach (var item in tupleList)
                {
                    result.Add(new MetaDataUpdateItem
                    {
                        UpdateType = metaUpdateType,
                        UpdateKey = updateKey,
                        OldValue = item.Item1,
                        NewValue = item.Item2,
                    });
                }
            }
            return result;
        }

        public static List<MetaDataUpdateItem> GetUpdateItems<T>(
            List<T> oldSources,
            List<T> newSources,
            Func<T, T, bool> identifierEqualFunc,
            Func<T, T, bool> bodyEqualFunc,
            MetaUpdateType metaUpdateType)
        {
            List<MetaDataUpdateItem> result = new List<MetaDataUpdateItem>();
            var flag = Compare(oldSources,
                                            newSources,
                                            identifierEqualFunc,
                                            bodyEqualFunc,
                                            out List<T> needAdd,
                                            out List<T> needDelete,
                                            out List<T> needUpdate,
                                            out List<Tuple<T, T>> needUpdateTuple);
            if (flag)
            {
                var items = GenerateUpdateItem<T>(needAdd, UpdateOpType.ADD, metaUpdateType);
                var items2 = GenerateUpdateItem<T>(needDelete, UpdateOpType.DELETE, metaUpdateType);
                var items3 = GenerateUpdateItem<T>(needUpdateTuple, UpdateOpType.UPDATE, metaUpdateType);
                result.AddRange(items);
                result.AddRange(items2);
                result.AddRange(items3);
            }
            return result;
        }

    }

    public enum UpdateOpType
    {
        ADD,
        UPDATE,
        DELETE
    }
}
